﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace WJP.Reflection
{
    /// <summary>
    /// 动态方法句柄工厂
    /// </summary>
    internal static class DynamicMethodHandlerFactory
    {
        public static DynamicMethodDelegate CreateMethod(MethodInfo method)
        {
            ParameterInfo[] parameters = method.GetParameters();
            DynamicMethod dynamicMethod = new DynamicMethod("dm", typeof(object), new Type[2]
            {
        typeof (object),
        typeof (object[])
            }, typeof(DynamicMethodHandlerFactory), true);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Ldarg_0);
            for (int index = 0; index < parameters.Length; ++index)
            {
                ilGenerator.Emit(OpCodes.Ldarg_1);
                ilGenerator.Emit(OpCodes.Ldc_I4, index);
                Type parameterType = parameters[index].ParameterType;
                if (parameterType.IsByRef)
                {
                    Type elementType = parameterType.GetElementType();
                    if (elementType.IsValueType)
                    {
                        ilGenerator.Emit(OpCodes.Ldelem_Ref);
                        ilGenerator.Emit(OpCodes.Unbox, elementType);
                    }
                    else
                        ilGenerator.Emit(OpCodes.Ldelema, elementType);
                }
                else
                {
                    ilGenerator.Emit(OpCodes.Ldelem_Ref);
                    if (parameterType.IsValueType)
                    {
                        ilGenerator.Emit(OpCodes.Unbox, parameterType);
                        ilGenerator.Emit(OpCodes.Ldobj, parameterType);
                    }
                }
            }
            if ((method.IsAbstract || method.IsVirtual) && !method.IsFinal && !method.DeclaringType.IsSealed)
                ilGenerator.Emit(OpCodes.Callvirt, method);
            else
                ilGenerator.Emit(OpCodes.Call, method);
            if (method.ReturnType == typeof(void))
                ilGenerator.Emit(OpCodes.Ldnull);
            else if (method.ReturnType.IsValueType)
                ilGenerator.Emit(OpCodes.Box, method.ReturnType);
            ilGenerator.Emit(OpCodes.Ret);
            return (DynamicMethodDelegate)dynamicMethod.CreateDelegate(typeof(DynamicMethodDelegate));
        }

        public static DynamicMemberGetDelegate CreatePropertyGetter(PropertyInfo property)
        {
            if (property == (PropertyInfo)null)
                throw new ArgumentNullException(nameof(property));
            if (!property.CanRead)
                return (DynamicMemberGetDelegate)null;
            MethodInfo getMethod = property.GetGetMethod();
            if (getMethod == (MethodInfo)null)
                getMethod = property.GetGetMethod(true);
            DynamicMethod dynamicMethod = new DynamicMethod("propg", typeof(object), new Type[1]
            {
        typeof (object)
            }, property.DeclaringType, true);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            if (!getMethod.IsStatic)
            {
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.EmitCall(OpCodes.Callvirt, getMethod, (Type[])null);
            }
            else
                ilGenerator.EmitCall(OpCodes.Call, getMethod, (Type[])null);
            if (property.PropertyType.IsValueType)
                ilGenerator.Emit(OpCodes.Box, property.PropertyType);
            ilGenerator.Emit(OpCodes.Ret);
            return (DynamicMemberGetDelegate)dynamicMethod.CreateDelegate(typeof(DynamicMemberGetDelegate));
        }

        public static DynamicMemberSetDelegate CreatePropertySetter(PropertyInfo property)
        {
            if (property == (PropertyInfo)null)
                throw new ArgumentNullException(nameof(property));
            if (!property.CanWrite)
                return (DynamicMemberSetDelegate)null;
            MethodInfo setMethod = property.GetSetMethod();
            if (setMethod == (MethodInfo)null)
                setMethod = property.GetSetMethod(true);
            DynamicMethod dynamicMethod = new DynamicMethod("props", (Type)null, new Type[2]
            {
        typeof (object),
        typeof (object)
            }, property.DeclaringType, true);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            if (!setMethod.IsStatic)
                ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldarg_1);
            DynamicMethodHandlerFactory.EmitCastToReference(ilGenerator, property.PropertyType);
            if (!setMethod.IsStatic && !property.DeclaringType.IsValueType)
                ilGenerator.EmitCall(OpCodes.Callvirt, setMethod, (Type[])null);
            else
                ilGenerator.EmitCall(OpCodes.Call, setMethod, (Type[])null);
            ilGenerator.Emit(OpCodes.Ret);
            return (DynamicMemberSetDelegate)dynamicMethod.CreateDelegate(typeof(DynamicMemberSetDelegate));
        }

        public static DynamicMemberGetDelegate CreateFieldGetter(FieldInfo field)
        {
            if (field == (FieldInfo)null)
                throw new ArgumentNullException(nameof(field));
            DynamicMethod dynamicMethod = new DynamicMethod("fldg", typeof(object), new Type[1]
            {
        typeof (object)
            }, field.DeclaringType, true);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            if (!field.IsStatic)
            {
                ilGenerator.Emit(OpCodes.Ldarg_0);
                DynamicMethodHandlerFactory.EmitCastToReference(ilGenerator, field.DeclaringType);
                ilGenerator.Emit(OpCodes.Ldfld, field);
            }
            else
                ilGenerator.Emit(OpCodes.Ldsfld, field);
            if (field.FieldType.IsValueType)
                ilGenerator.Emit(OpCodes.Box, field.FieldType);
            ilGenerator.Emit(OpCodes.Ret);
            return (DynamicMemberGetDelegate)dynamicMethod.CreateDelegate(typeof(DynamicMemberGetDelegate));
        }

        public static DynamicMemberSetDelegate CreateFieldSetter(FieldInfo field)
        {
            if (field == (FieldInfo)null)
                throw new ArgumentNullException(nameof(field));
            DynamicMethod dynamicMethod = new DynamicMethod("flds", (Type)null, new Type[2]
            {
        typeof (object),
        typeof (object)
            }, field.DeclaringType, true);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            if (!field.IsStatic)
                ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldarg_1);
            DynamicMethodHandlerFactory.EmitCastToReference(ilGenerator, field.FieldType);
            if (!field.IsStatic)
                ilGenerator.Emit(OpCodes.Stfld, field);
            else
                ilGenerator.Emit(OpCodes.Stsfld, field);
            ilGenerator.Emit(OpCodes.Ret);
            return (DynamicMemberSetDelegate)dynamicMethod.CreateDelegate(typeof(DynamicMemberSetDelegate));
        }

        private static void EmitCastToReference(ILGenerator il, Type type)
        {
            if (type.IsValueType)
                il.Emit(OpCodes.Unbox_Any, type);
            else
                il.Emit(OpCodes.Castclass, type);
        }
    }
}
